// This program implements a vector addition using OpenCL

// System includes
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "adcUtilsOpenCL.h"

// OpenCL includes
#include <CL/cl.h>

int main(int argc, char* argv[]){
    // This code executes on the OpenCL host

    // Host data

    /*Modo express o real-time*/
    bool realTimeModeOn;
    if (strcmp(argv[1], "r") == 0) {
        realTimeModeOn = true;
        printf("Mode Real Time selected\n");
    } else if (strcmp(argv[1], "e") == 0) {
        realTimeModeOn = false;
        printf("Mode Express selected\n");
    }else{
        printf("Mode no selected\n");
    }

    /*Tamaño de la matriz*/
    int N = atoi(argv[2]);

    /*Cantidad de iteraciones*/
    int k = atoi(argv[3]);

    /*Cantidad de fuentes de calor*/
    /*En el ejercicio se sujeria usar como nombre de variable la letra "j", se ovio esto porque es mas comodo usar
    esa variable en loops. Se renombro como "tempCounter" (contador de temperaturas)*/
    int tempCounter = atoi(argv[4]);

    int i, j;
    int offset;

    /*Usado para input*/
    float foil[N][N];
    /*Usado para output o input/output*/
    float result[N][N];

    /*Inicializo la matriz de entrada. Se supone temperatura incial 0ºC*/
    for (i = 0; i < N; ++i) {
        for (j = 0; j < N; ++j) {
            foil[i][j] = 0.0;
        }
    }

    for (i = 0; i < tempCounter; i++) {
        offset=i*3+5;/* *3 porque come de a 3 elementos, +5 porquelos primeros 5 parametros no sirven para esto*/
        /*foil(x,y)=t*/
        foil[atoi(argv[offset])][atoi(argv[offset+1])] = atof(argv[offset+2]);
    }

    for(i=0; i<N; i++){
        for(j=0; j<N; j++){
            printf("%f ", foil[i][j]);
        }
        printf("\n");
    }
    size_t datasize = N*N*sizeof(float);

    //———————————————————————————————————————————————————
    // STEP 1: Set Up Environment
    //———————————————————————————————————————————————————

    // Use this to check the output of each API call
    cl_int ciErrNum;

    cl_platform_id platform;

    // Use the first platforms
    ciErrNum = clGetPlatformIDs(1, &platform, NULL);

    if(ciErrNum == CL_SUCCESS){
        printf("First platform chosen...\n");
    }else{
        printf("Error platform chosen\n");
    }

    cl_device_id device;

    // Use the first device
    ciErrNum = clGetDeviceIDs(platform, CL_DEVICE_TYPE_ALL, 1, &device, NULL);

    if(ciErrNum == CL_SUCCESS){
        printf("First device chosen...\n");
    }else{
        printf("Error device chosen\n");
    }

    cl_context_properties cps[3] = {
        CL_CONTEXT_PLATFORM, (cl_context_properties)platform, 0 };

    // Create the context
    cl_context ctx;
    ctx = clCreateContext(cps, 1, &device, NULL, NULL, &ciErrNum);

    if(ciErrNum == CL_SUCCESS){
        printf("Context created\n");
    }else{
        printf("Error creating context\n");
    }

    // Create the command queue
    cl_command_queue myqueue;
    myqueue = clCreateCommandQueue(ctx, device, 0, &ciErrNum);

    if(ciErrNum == CL_SUCCESS){
        printf("Command queue created\n");
    }else{
        printf("Error creating command queue\n");
    }

    //———————————————————————————————————————————————————
    // STEP 2: Declare Buffers and Move Data
    //———————————————————————————————————————————————————

    // Create a buffer object that will contain the data
    // from the host array foil
    cl_mem bufFoil;
    bufFoil = clCreateBuffer(ctx, CL_MEM_READ_ONLY, datasize, NULL, &ciErrNum);

    if(ciErrNum == CL_SUCCESS){
        printf("Buffer created\n");
    }else{
        printf("Error creating buffer\n");
    }

    // to the host array result
    cl_mem bufResult;
    bufResult = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, tempCounter*sizeof(int), NULL, &ciErrNum);

    if(ciErrNum == CL_SUCCESS){
        printf("Buffer created\n");
    }else{
        printf("Error creating buffer\n");
    }

    // Write input array foil to the device buffer bufferA
    ciErrNum = clEnqueueWriteBuffer(myqueue, bufFoil, CL_TRUE, 0, datasize, 
                                  (void *)foil, 0, NULL, NULL);

    if(ciErrNum == CL_SUCCESS){
        printf("Command EnqueueWriteBuffer OK\n");
    }else{
        printf("ERROR EnqueueWriteBuffer\n");
    }

    //———————————————————————————————————————————————————
    // STEP 3: Runtime Kernel Compilation
    //———————————————————————————————————————————————————

    char* kernelPath = "kernelExpress.cl";

    char* programSource = readSource(kernelPath);

    // Create a program with source code
    cl_program myprog = clCreateProgramWithSource(ctx, 1,
                                                   (const char**)&programSource,
                                                   NULL, &ciErrNum);

    if(ciErrNum == CL_SUCCESS){
        printf("Program with source created\n");
    }else{
        printf("Error creating program with source\n");
    }

    // Build (compile) the program for the device
    ciErrNum = clBuildProgram(myprog, 1, &device, NULL, NULL, NULL);

    if(ciErrNum == CL_SUCCESS){
        printf("Program built\n");
    }else{
        printf("Error biulding program. Number error: %u\n", ciErrNum);
    }

    // Create the vector addition kernel
    cl_kernel mykernel;
    mykernel = clCreateKernel(myprog, "express", &ciErrNum);

    if(ciErrNum == CL_SUCCESS){
        printf("Kernel created\n");
    }else{
        printf("Error creating Kernel\n");
    }

    //———————————————————————————————————————————————————
    // STEP 4: Run the program
    //———————————————————————————————————————————————————

    // Associate the input and output buffers with the kernel
    clSetKernelArg(mykernel, 0, sizeof(cl_mem), (void *)&bufFoil);
    clSetKernelArg(mykernel, 1, sizeof(cl_mem), (void *)&bufResult);
    clSetKernelArg(mykernel, 2, sizeof(cl_int), (void *)&N);
    clSetKernelArg(mykernel, 3, sizeof(cl_int), (void *)&k);

    // Set local and global workgroup sizes
    size_t localWS[2] = {N,N};
    size_t globalWS[2] = {N,N};

    // Execute kernel
    ciErrNum = clEnqueueNDRangeKernel(myqueue, mykernel, 2, NULL, globalWS, 
                                      localWS, 0, NULL, NULL);

    if(ciErrNum == CL_SUCCESS){
        printf("Enqueue Range Kernel created\n");
    }else{
        printf("Error creating Enqueue Range Kernel\n");
    }

    //———————————————————————————————————————————————————
    // STEP 5: Return result to host
    //———————————————————————————————————————————————————

    // Read the device output buffer to the host output array
    ciErrNum = clEnqueueReadBuffer(myqueue, bufResult, CL_TRUE, 0, datasize, 
                        (void *)result, 0, NULL, NULL);

    if(ciErrNum == CL_SUCCESS){
        printf("Buffer read\n");
    }else{
        printf("Error reading buffer\n");
    }

    /*OpenCL Matriz*/
    printf("Matriz\n");
    for(i = 0; i < N; i++) {
        for(j = 0; j < N; j++){
            printf("%f ",result[i][j]);
        }
        printf("\n");
    }

    //———————————————————————————————————————————————————
    // STEP 6: Release OpenCL resources
    //———————————————————————————————————————————————————

    // Free OpenCL resources
    clReleaseKernel(mykernel);
    clReleaseProgram(myprog);
    clReleaseCommandQueue(myqueue);
    clReleaseMemObject(bufFoil);
    clReleaseMemObject(bufResult);
    clReleaseContext(ctx);

    return 0;
}
